package _1_常用数据类型_关键字;

import org.junit.Test;

// import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.openjdk.jol.vm.VM;

/**
 * @see #unicode()                        String 存储格式
 * @see #aboutStringTable()               关于字符串常量池
 * @see #compareStringBuilderBuffer()     String, StringBuilder, StringBuffer
 * @see #formatString()                   字符串格式化
 */
public class _3_AboutString {

  /**
   * JDK8,及之前   String 的值以 char[] 存储，这个 [] 的 每一个 char 以 UTF-16 编码保存在内存中，一个 char 16bit
   * 从 JDK9 开始，将 String 的底层实现由 char[] 改成了 byte[]，并且添加了一个字段保存字符串的编码格式 {@link String#coder}
   * 主要是为了节省空间
   * jdk9为何要将String的底层实现由char[]改成了byte[]? - basic13的回答 - 知乎
   *  https://www.zhihu.com/question/447224628/answer/1759986003
   */
  @Test
  public void unicode() throws UnsupportedEncodingException {
    System.out.printf("%d. %s%s%n", 1, "hahha", ";");

    String a = "ABCD";
    // JDK <= 8:   a.value 是 char[4] : [0x0041, 0x0042, 0x0043, 0x0044]
    // JDK >= 9:   a.value 是 byte[4] : [0x41,   0x42,   0x43,   0x44]       只有 Latin-1 字符，节省一半空间
    String b = "一生二";
    // JDK <= 8:    char[] : [0x4E00,     0x751F,     0x4E8C]
    // JDK >= 9:    byte[] : [0x00,0x4E,  0x1F,0x75,  0x8C,0x4E]        不是只有 Latin-1 字符，和原来空间大小一致
    String c = "A一";
    // JDK <= 8:    char[] : [0x0041,     0x4E00]
    // JDK >= 9:    byte[] : [0x41,0x00,  0x00,0x4E]
    System.out.println("🀎".length());
    System.out.println("🀎".charAt(0));
    System.out.println("🀎".charAt(1));

    byte[] bytes = a.getBytes("GB2312");
    // 使用 getBytes()方法按指定编码把 char[] 转为 byte[] 用于传输
    // 此时 byte[] 的编码格式是指定的 GB2312 编码

    String gbk = new String(bytes, "GBK");
    // 这里可以正常把 byte[] 转为 char[] , 因为指定的编码格式可以用
    System.out.println(gbk);

    String x1 = new String(bytes, "ISO8859-1");
    // 这里会乱码，因为上面有中文，byte[] 的编码是 GB2312,
    // byte[] 转 char[]时, 不适用的编码会造成字符串乱码
    System.out.println(x1);

    // http header中必须是ASCII字符, 要用 URLEncoder.encode 进行转码
    String encode = URLEncoder.encode(a, "utf-8");
    System.out.println(encode);

    String str = "\u005B\u00A0\u005D";
    System.out.println(str);

    System.out.println("\u4e0d\u80fd\u4e3a\u7a7a");

    System.out.println("希腊字母\u03B1\u03CB\u03CC");

    byte[] bytes2 = "中文3".getBytes("utf-8");
    for (byte by : bytes2) {
      int v = (int) by;
      String s = Integer.toBinaryString(v);
      System.out.println(("00000000" + s).substring(s.length()));
    }
    str = new String(bytes2, "utf-8");
    System.out.println(str);

    for (int i = 0; i < str.length(); i++) {
      char ch = str.charAt(i);
      int v = (int) ch;
      System.out.println(v);
      String s = Integer.toBinaryString(v);
      String x = "00000000" + s;
      System.out.println(x.substring(s.length()));
    }

    // \u0022 就是 " 号, \u0022 先转为 " , 然后变成 ""+""
    // 而不是先 执行 +
    System.out.println("\u0022+\u0022");

    // 转义字符
    // \u00A0 is a newline
  }


  /**
   * <pre>
   * 使用 new String("你") 实际上相当于：
   *     String original = "你";
   *     String str = new String(original);
   * 所以 jvm中会创建两个对象：一个存放在 字符串常量池中, 一个不在字符串常量池中 ,
   *     str.intern() 会返回那个在常量池中的引用, 所以不相等
   *
   * 使用 new StringBuilder().toString 同样是调用了 new String, 结果一样
   * </pre>
   */
  public void createStringUseNew() {
    String str = new String("你");
    System.out.println(str == str.intern());
    String str1 = new StringBuilder("你1").toString();
    System.out.println(str1 == str1.intern());
  }

  /**
   * 使用 "" 字面量直接创建 String, 会把这个对象直接放入 字符串常量池 (String Table) 使用 "..." + "..." 会进行编译优化, 同上
   */
  private void createStringUseLiteral() {
    String str = "我";
    System.out.println(str == str.intern());
    String str1 = "我" + "1";
    System.out.println(str1 == str1.intern());
  }

  /**
   * <pre>
   * String str3 = new StringBuilder("计算机").append("软件").toString();
   * 上面代码执行完后：
   *           String Table： 存放里两个字符串:  "计算机"   "软件"
   *    堆 (非String Table):  存放一个字符串:   "计算机软件"
   *
   * String Table中没有 "计算机软件" 所以：
   *    str3 和 str3.intern() 是相等的
   *
   * 其效果等同于：
   *    String str3 = new String("计算机") + new String("软件");
   * </pre>
   */
  private void createStringUseAppend() {
    String str3 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str3 == str3.intern());
    String str4 = new StringBuilder("ja").append("va").toString();
    System.out.println(str4.intern() == str4);
  }

  /**
   * <pre>
   * JDK1.6中,  字符串常量池 (String Table) 保存在 方法区 (Method Area) 的常量池中
   * >JDK1.7中, 移除了 方法区 (Method Area), 字符串常量池 (String Table) 移动到了 堆 (Heap) 中
   * </pre>
   */
  @Test
  public void aboutStringTable() {

    System.out.println("使用 new 构造器");
    createStringUseNew();
    System.out.println("使用 \"\" 字面量");
    createStringUseLiteral();
    System.out.println("使用 append");
    createStringUseAppend();

    System.out.println("-----------------------------------------------");

    String s1 = "关于字符串常量池";
    String s2 = "关于字符串常量池";
    System.out.println(s1 == s2);
    String s3 = new String("关于字符串常量池");
    String s4 = new String("关于字符串常量池");
    System.out.println(s1 == s3);
    System.out.println(s3 == s4);
    System.out.println(s1 == s3.intern());
    System.out.println("s1的虚拟内存地址：" + VM.current().addressOf(s1));
    System.out.println("s2的虚拟内存地址：" + VM.current().addressOf(s2));
    System.out.println("s3的虚拟内存地址：" + VM.current().addressOf(s3));
    System.out.println("s4的虚拟内存地址：" + VM.current().addressOf(s4));

  }


  /**
   * <pre>
   * 运行速度: StringBuilder > StringBuffer > String
   * 原因:
   *     String        {@link String}         字符串常量, 创建后不能实质更改, 对 String的更改, 实际是 jvm创建了另一个新 String对象,并回收旧对象
   *     StringBuilder {@link StringBuilder}  字符串变量, 非线程安全
   *     StringBuffer  {@link StringBuffer}   字符串变量, 线程安全
   * 因此:
   *     String：       适用于 少量的字符串操作
   *     StringBuilder：适用于 单线程 下在字符缓冲区进行大量操作, 例如: sql语句的拼接上
   *     StringBuffer： 适用于 多线程 下在字符缓冲区进行大量操作
   * </pre>
   */
  @Test
  public void compareStringBuilderBuffer() {

    // 测试速度：10000次循环
    long time1 = System.currentTimeMillis();

    String str = "abc";
    for (int i = 0; i < 20000; i++) {
      str = str + "def";
    }
    long time2 = System.currentTimeMillis();

    for (int i = 0; i < 20000; i++) {
      str = str.concat("def");
    }
    long time3 = System.currentTimeMillis();

    StringBuilder stringBuilder = new StringBuilder().append("abc");
    for (int i = 0; i < 20000; i++) {
      stringBuilder.append("def");
    }
    long time4 = System.currentTimeMillis();

    System.out.println(time2 - time1);      // String + String
    System.out.println(time3 - time2);      // String.concat(String)
    System.out.println(time4 - time3);      // StringBuilder.append
  }

  @Test
  public void reverseString() {
    StringBuilder builder = new StringBuilder("1234");
    System.out.println(builder.reverse().toString());
  }

  /**
   * 字符格式化技巧
   */
  @Test
  public void formatString() {
    // String 字符前补0， 格式化
    String substring = ("00000000" + "Apple").substring("Apple".length());
    System.out.println(substring);

    String s = String.format("%02d", 1000);
    System.out.println(s);
  }


  @Test
  public void hexStringToBytes() {
    long start = System.currentTimeMillis();
    // 使用 自己写的的方法
    for (int i = 0; i < 50000; i++) {
      hexStringToByteArray(
          "0c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c0118640101060006005004");
    }
    long end1 = System.currentTimeMillis();

    // 使用 java 原生库带的方法， JDK11去掉了
    for (int i = 0; i < 50000; i++) {
      // DatatypeConverter.parseHexBinary(
      //     "0c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c01186401010600060050040c0118640101060006005004");
    }

    long end2 = System.currentTimeMillis();
    System.out.println(end1 - start);
    System.out.println(end2 - end1);

  }

  public static byte[] hexStringToByteArray(String s) {
    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
      data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
          + Character.digit(s.charAt(i + 1), 16));
    }
    return data;
  }

  @Test
  public void test0() {
    String a = (String) null;
    System.out.println(a);
  }
}
